﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Microsoft.ResourceManagement.ObjectModel
{
    /// <summary>
    /// Represents the base Resource object type.
    /// 
    /// This class is a weakly-typed property bag implemented as a dictionary.
    /// Some attributes like ObjectId, DisplayName, and ObjectType have been propomoted to .NET properties.
    /// 
    /// Derived object types like RmUser can promote other properties which are specific to those object types.
    /// 
    /// Use a RmResourceTransaction to monitor changes made to resource objects.
    /// </summary>
    public class RmResource : IDictionary<RmAttributeName, RmAttributeValue>, IDisposable
    {
        internal Dictionary<RmAttributeName, RmAttributeValue> attributes;

        public RmResource()
        {
            this.attributes = new Dictionary<RmAttributeName, RmAttributeValue>();
            this.EnsureAllAttributesExist();

            this.ObjectType = this.GetResourceType();
        }

        protected virtual void EnsureAllAttributesExist()
        {
            this.EnsureAttributeExists(AttributeNames.CreatedTime);
            this.EnsureAttributeExists(AttributeNames.Creator);
            this.EnsureAttributeExists(AttributeNames.DeletedTime);
            this.EnsureAttributeExists(AttributeNames.Description);
            this.EnsureAttributeExists(AttributeNames.DetectedRulesList);
            this.EnsureAttributeExists(AttributeNames.DisplayName);
            this.EnsureAttributeExists(AttributeNames.ExpectedRulesList);
            this.EnsureAttributeExists(AttributeNames.ExpirationTime);
            this.EnsureAttributeExists(AttributeNames.Locale);
            this.EnsureAttributeExists(AttributeNames.MVObjectID);
            this.EnsureAttributeExists(AttributeNames.ObjectID);
            this.EnsureAttributeExists(AttributeNames.ObjectType);
            this.EnsureAttributeExists(AttributeNames.ResourceTime);
        }

        public virtual String GetResourceType()
        {
            return @"Resource";
        }

        public Dictionary<RmAttributeName, RmAttributeValue> Attributes
        {
            get
            {
                return this.attributes;
            }
        }

        protected String GetString(RmAttributeName attributeName)
        {
            Object o = null;
            RmAttributeValue rma = null;
            this.TryGetValue(attributeName, out rma);
            if (rma != null)
                o = rma.Value;
            if (o == null)
            {
                return String.Empty;
            }
            else
            {
                return (String)o;
            }
        }

        protected RmReference GetReference(RmAttributeName attributeName)
        {
            IComparable o = null;
            RmAttributeValue rma = null;
            this.attributes.TryGetValue(attributeName, out rma);
            if (rma != null && rma.Value != null)
                o = rma.Value;
            return o as RmReference;
        }

        protected bool GetBoolean(RmAttributeName attributeName)
        {
            Object o = null;
            RmAttributeValue rma = null;
            this.attributes.TryGetValue(attributeName, out rma);
            if (rma != null)
                o = rma.Value;
            if (o == null)
            {
                return false;
            }
            else
            {
                return (bool)o;
            }
        }

        protected int GetInteger(RmAttributeName attributeName)
        {
            Object o = null;
            RmAttributeValue rma = null;
            this.attributes.TryGetValue(attributeName, out rma);
            if (rma != null)
                o = rma.Value;
            if (o == null)
            {
                return 0;
            }
            else
            {
                return (Int32)o;
            }

        }

        protected RmList<RmReference> GetMultiValuedReference(RmAttributeName attributeName)
        {
            IList<IComparable> o = null;
            RmAttributeValue rma = null;
            this.attributes.TryGetValue(attributeName, out rma);
            if (rma == null)
            {
                rma = new RmAttributeValue();
                this.attributes[attributeName] = rma;
            }
            o = rma.Values;
            if (o == null)
            {
                return null;
            }
            else
            {
                return new RmList<RmReference>(o);
            }
        }

        #region promoted properties

        public RmReference ObjectID
        {
            get
            {
                return GetReference(AttributeNames.ObjectID);
            }
            set
            {
                this.attributes[AttributeNames.ObjectID].Value = value;
            }
        }

        public String ObjectType
        {
            get
            {
                return GetString(AttributeNames.ObjectType);
            }
            set
            {
                this[AttributeNames.ObjectType].Value = value;
            }
        }

        public String DisplayName
        {
            get
            {
                return GetString(AttributeNames.DisplayName);
            }
            set
            {
                this[AttributeNames.DisplayName].Value = value;
            }
        }

        public String Locale
        {
            get
            {
                return GetString(AttributeNames.Locale);
            }
            set
            {
                this[AttributeNames.Locale].Value = value;
            }
        }

        public String Description
        {
            get
            {
                return GetString(AttributeNames.Description);
            }
            set
            {
                this[AttributeNames.Description].Value = value;
            }
        }

        #endregion

       protected void EnsureAttributeExists(RmAttributeName attributeName)
        {
            EnsureNotDisposed();
            lock (this.attributes)
            {
                if (attributeName == null)
                {
                    throw new ArgumentNullException("attributeName");
                }
                if (this.attributes.ContainsKey(attributeName))
                {
                    return;
                }
                else
                {
                    this.attributes.Add(attributeName, new RmAttributeValue());
                }
            }
        }

        #region Object
        public override bool Equals(object obj)
        {
            RmResource other = obj as RmResource;
            if (other == null)
            {
                return false;
            }
            else
            {
                if (this.attributes.Count != other.attributes.Count)
                {
                    return false;
                }
                foreach (KeyValuePair<RmAttributeName, RmAttributeValue> item in this.attributes)
                {
                    RmAttributeValue otherValue = null;
                    other.TryGetValue(item.Key, out otherValue);
                    if (otherValue == null)
                        return false;
                    if (item.Value.Equals(otherValue) == false)
                        return false;
                }
                return true;
            }
        }

        public override int GetHashCode()
        {
            return this.ObjectID.GetHashCode();
        }

        public override string ToString()
        {
            return String.Format("{0}:{1}",
                this.ObjectType,
                this.ObjectID.ToString()
            );
        }
        #endregion

        #region IDictionary<RmAttributeName,RmAttributeValue> Members

        public void Add(RmAttributeName key, RmAttributeValue value)
        {
            EnsureNotDisposed();
            this.attributes.Add(key, value);
        }

        public bool ContainsKey(RmAttributeName key)
        {
            EnsureNotDisposed();
            return this.attributes.ContainsKey(key);
        }

        public ICollection<RmAttributeName> Keys
        {
            get
            {
                EnsureNotDisposed();
                return this.attributes.Keys;
            }
        }

        public bool Remove(RmAttributeName key)
        {
            EnsureNotDisposed();
            return this.attributes.Remove(key);
        }

        public bool TryGetValue(RmAttributeName key, out RmAttributeValue value)
        {
            EnsureNotDisposed();
            return this.attributes.TryGetValue(key, out value);
        }

        public ICollection<RmAttributeValue> Values
        {
            get
            {
                EnsureNotDisposed();
                return this.attributes.Values;
            }
        }

        public RmAttributeValue this[String key]
        {
            get
            {
                EnsureNotDisposed();
                return this.attributes[new RmAttributeName(key)];
            }
            set
            {
                EnsureNotDisposed();
                RmAttributeName myKey = new RmAttributeName(key);
                this.attributes[myKey] = value;
            }
        }

        public RmAttributeValue this[RmAttributeName key]
        {
            get
            {
                EnsureNotDisposed();
                return this.attributes[key];
            }
            set
            {
                EnsureNotDisposed();
                this.attributes[key] = value;
            }
        }

        #endregion

        #region ICollection<KeyValuePair<RmAttributeName,RmAttributeValue>> Members

        public void Add(KeyValuePair<RmAttributeName, RmAttributeValue> item)
        {
            EnsureNotDisposed();
            this.Add(item.Key, item.Value);
        }

        public void Clear()
        {
            EnsureNotDisposed();
            this.attributes.Clear();
        }

        public bool Contains(KeyValuePair<RmAttributeName, RmAttributeValue> item)
        {
            EnsureNotDisposed();
            return this.attributes.ContainsKey(item.Key) && this.attributes[item.Key].Value.Equals(item.Value);
        }

        public void CopyTo(KeyValuePair<RmAttributeName, RmAttributeValue>[] array, int arrayIndex)
        {
            EnsureNotDisposed();
            throw new NotImplementedException();
        }

        public int Count
        {
            get
            {
                EnsureNotDisposed();
                return this.attributes.Count;
            }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(KeyValuePair<RmAttributeName, RmAttributeValue> item)
        {
            EnsureNotDisposed();
            return this.attributes.Remove(item.Key);
        }

        #endregion

        #region IEnumerable<KeyValuePair<RmAttributeName,RmAttributeValue>> Members

        public IEnumerator<KeyValuePair<RmAttributeName, RmAttributeValue>> GetEnumerator()
        {
            EnsureNotDisposed();
            return this.attributes.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            EnsureNotDisposed();
            return this.attributes.GetEnumerator();
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            EnsureNotDisposed();
            lock (this.attributes)
            {
                this.attributes.Clear();
                this.attributes = null;
                GC.SuppressFinalize(this);
            }
        }

        private void EnsureNotDisposed()
        {
            if (this.attributes == null)
            {
                throw new ObjectDisposedException("RmObject", "The RmObject object has already been disposed");
            }
        }

        #endregion

        public sealed class AttributeNames
        {
            public static RmAttributeName CreatedTime = new RmAttributeName(@"CreatedTime");
            public static RmAttributeName Creator = new RmAttributeName(@"Creator");
            public static RmAttributeName DeletedTime = new RmAttributeName(@"DeletedTime");
            public static RmAttributeName Description = new RmAttributeName(@"Description");
            public static RmAttributeName DetectedRulesList = new RmAttributeName(@"DetectedRulesList");
            public static RmAttributeName DisplayName = new RmAttributeName(@"DisplayName");
            public static RmAttributeName ExpectedRulesList = new RmAttributeName(@"ExpectedRulesList");
            public static RmAttributeName ExpirationTime = new RmAttributeName(@"ExpirationTime");
            public static RmAttributeName Locale = new RmAttributeName(@"Locale");
            public static RmAttributeName MVObjectID = new RmAttributeName(@"MVObjectID");
            public static RmAttributeName ObjectID = new RmAttributeName(@"ObjectID");
            public static RmAttributeName ObjectType = new RmAttributeName(@"ObjectType");
            public static RmAttributeName ResourceTime = new RmAttributeName(@"ResourceTime");
        }
    }
}
